
#include "bloque_sec.h"

BloqueSec::BloqueSec (Bloque* bloque, TipoStruct* base){
	this->bloque = bloque;
	this->reg_base = (TipoStruct*) base->clonar();
	
	//Si el bloque esta vacio, le meto 0 a la cant de registros
	if (this->bloque->get_tope() == 0)
		set_cant_regs(0);
}

BloqueSec::~BloqueSec(){
	if (this->reg_base != NULL)
		delete (this->reg_base);	
}

unsigned int BloqueSec::get_esp_libre_bloque(){
	return this->bloque->get_espacio_libre();	
}

unsigned int BloqueSec::get_tam_ocupado(){
	return (this->bloque->get_tope() - PRIMERO_BLOQUE_SEC);	
}

void BloqueSec::set_cant_regs(unsigned int cant_regs){
	//Escribo la cant de regs en el bloque en la pos relativa 0.
	this->bloque->escribir((char*) &cant_regs, 0, sizeof(unsigned int));	
}
	
unsigned int BloqueSec::get_cant_regs(){
	//Leo el nivel desde el bloque en la pos relativa 0.
	unsigned int resultado = 0;
	this->bloque->leer((char*) &resultado, 0, sizeof(unsigned int));
	return resultado;
}
		
TipoStruct* BloqueSec::get_registro(TipoDato* clave){
	//Busco el registro. Si esta lo obtengo.
	unsigned int pos = PRIMERO_BLOQUE_SEC;
	bool encontrado = this->buscar_registro(clave, pos);
	TipoStruct* resultado = NULL;
	if (encontrado) {
		resultado = (TipoStruct*) this->reg_base->clonar();
		this->bloque->get_registro(resultado, pos);	
	}
	return resultado;
}

TipoDato* BloqueSec::obtener_primera_clave(){
	TipoStruct* aux = NULL;
	TipoDato* result = NULL;
	if (!this->vacio()) {
		aux = (TipoStruct*)this->reg_base->clonar();
		this->bloque->get_registro(aux, PRIMERO_BLOQUE_SEC);
		result = ((*aux)[0]).clonar();
		delete (aux);
	}
	return result;
}

bool BloqueSec::ingresar_registro(TipoStruct* reg){
	unsigned int pos = PRIMERO_BLOQUE_SEC;
	//Si entra el reg en el bloque
	if (this->bloque->get_espacio_libre() >= reg->get_tamanio_serializado()) {
		//Si no esta vacio
		if (!this->vacio()) {
			//Busco el registro. Si esta devuelvo false.
			bool encontrado = this->buscar_registro(&((*reg)[0]), pos);
			if (encontrado)
				return false;
			//Sino expando el bloque desde pos en tam del registro a insertar
			this->bloque->expandir(pos, reg->get_tamanio_serializado());
		}
		//Escribo el registro en pos
		this->bloque->escribir_registro(reg, pos);
		//Incremento la cant de regs en el bloque
		this->set_cant_regs(this->get_cant_regs() + 1);
		return true;
	}
	//Si no entro devuelvo false
	return false;
}


bool BloqueSec::quitar_registro (TipoDato* clave){
	//Busco el registro. Si esta lo obtengo.
	unsigned int pos = PRIMERO_BLOQUE_SEC;
	bool encontrado = this->buscar_registro(clave, pos);
	TipoStruct* resultado = NULL;
	if (encontrado) {
		//Si lo encontre hago un get registro y comprimo el bloque
		resultado = (TipoStruct*) this->reg_base->clonar();
		this->bloque->get_registro(resultado, pos);
		this->bloque->comprimir(pos, resultado->get_tamanio_serializado());
		//Decremento la cant de regs en el bloque
		this->set_cant_regs(this->get_cant_regs() - 1);
		delete(resultado);
		return true;
	}
	return false;
}

bool BloqueSec::buscar_registro (TipoDato* clave, unsigned int &pos){
	pos = PRIMERO_BLOQUE_SEC;
	//Si el bloque no esta vacio
	if (!this->vacio()) {
		//Clono el reg base, arranco desde la pos 0
		TipoStruct* aux = (TipoStruct*) this->reg_base->clonar();
		unsigned int pos_act = PRIMERO_BLOQUE_SEC;
		pos = pos_act;
		bool encontrado = false;
		bool ok = this->bloque->get_registro(aux, pos_act);
		//Busco hasta que lo encuentre o lea uno mayor. Si llegue al final termino.
		while ((ok) && (!encontrado)) {
			if ((*clave) == ((*aux)[0]))
				encontrado = true;
			else
				if ((*clave) < ((*aux)[0]))
					ok = false;
			if ((!encontrado) && (ok)) {
				pos_act += aux->get_tamanio_serializado();
				pos = pos_act;
				delete(aux);
				aux = (TipoStruct*)this->reg_base->clonar();
				ok = this->bloque->get_registro(aux, pos_act);
			}
		} 
		delete (aux);
		//Devuelvo si lo encontre o no
		return encontrado;
	}
	return false;
}

bool BloqueSec::vacio() {
	return (this->bloque->get_tope() == PRIMERO_BLOQUE_SEC);
}

void BloqueSec::vaciar() {
	this->bloque->vaciar();
}
  
Bloque* BloqueSec::get_bloque() {
	return this->bloque;
}
  
void BloqueSec::ingresar_con_split(BloqueSec* bloque_sec, TipoStruct* reg){
	TipoStruct* reg_aux; //Vble aux para el manejo de registros
	unsigned int tam_reg;
	Bloque* aux;
	Bloque* bq_con_oflow;
	char* contenido;

	if (reg != NULL) {
		tam_reg = reg->get_tamanio_serializado();
		aux = this->bloque; //Guardo una ref al bloque actual
		//Creo un bloque con una zona de overflow e ingreso el dato nuevo y se lo asigno como bloque del nodo
		bq_con_oflow = new Bloque(aux->get_num_bloque(), aux->get_size() + tam_reg);
		contenido = new char[aux->get_tope()];
		aux->leer(contenido, 0, aux->get_tope());
		bq_con_oflow->vaciar();
		bq_con_oflow->escribir(contenido, 0, aux->get_tope());
		delete[] (contenido);
		this->bloque = bq_con_oflow;
		this->ingresar_registro(reg);
	}
	
	//Voy a la pos de la mitad
	unsigned int pos = this->get_mitad();
	
	//recorro los elementos siguientes al del medio, los quito y los meto en el bq nuevo
	reg_aux = (TipoStruct*) reg_base->clonar();
	this->bloque->get_registro(reg_aux, pos);
	while (pos < this->bloque->get_tope()) {
		bloque_sec->ingresar_registro(reg_aux);
		this->quitar_registro(&((*reg_aux)[0]));
		delete(reg_aux);
		reg_aux = (TipoStruct*) reg_base->clonar();
		this->bloque->get_registro(reg_aux, pos);
	}
	delete (reg_aux);
	if (reg != NULL) {
		//Copio el bq con overflow al anterior y lo vuelvo a meter en el bloque para que quede como antes
		//pero con la mitad mas baja
		contenido = new char[bq_con_oflow->get_tope()];
		bq_con_oflow->leer(contenido, 0, bq_con_oflow->get_tope());
		aux->vaciar();
		aux->escribir(contenido, 0, bq_con_oflow->get_tope());
		delete[](contenido);
		this->bloque = aux;
		delete(bq_con_oflow);
	}
}

TipoDato * BloqueSec::get_reg_base(){
	return this->reg_base->clonar();	
}

bool BloqueSec::unir(BloqueSec * bloque_sec) {
	unsigned int esp_libre = this->get_esp_libre_bloque();
	unsigned int esp_ocupado_bloque_param = bloque_sec->get_bloque()->get_tope();
	TipoStruct* reg_aux;
	if (esp_ocupado_bloque_param <= esp_libre) {
		while (!bloque_sec->vacio()) {
			reg_aux = (TipoStruct*)this->get_reg_base();
			bloque_sec->get_bloque()->get_registro(reg_aux, PRIMERO_BLOQUE_SEC);
			this->ingresar_registro(reg_aux);
			//Quito el registro del bloque pasado como param
			bloque_sec->quitar_registro(&((*reg_aux)[0]));
			delete(reg_aux);
		}
		return true;
	}
	return false;
}

void BloqueSec::equilibrar_bloques (BloqueSec* bloque_sec){
	
	Bloque* aux = this->bloque; //Guardo una ref al bloque actual
	//Creo un super bloque con el doble de tamanio del original y le copio el contenido y lo seteo
	Bloque* bq_con_oflow = new Bloque(aux->get_num_bloque(), this->bloque->get_size() + bloque_sec->bloque->get_size());
	char* contenido = new char[aux->get_tope()];
	aux->leer(contenido, 0, aux->get_tope());
	bq_con_oflow->vaciar();
	bq_con_oflow->escribir(contenido, 0, aux->get_tope());
	delete[] (contenido);
	this->bloque = bq_con_oflow;
	
	//Uno this con el bloque sec
	this->unir(bloque_sec);
	//Hago un split con bloque sec
	this->ingresar_con_split(bloque_sec, NULL);
	
	//Copio el bq con overflow al anterior y lo vuelvo a meter en el bloque para que quede como antes
	//pero con la mitad mas baja
	contenido = new char[bq_con_oflow->get_tope()];
	bq_con_oflow->leer(contenido, 0, bq_con_oflow->get_tope());
	aux->vaciar();
	aux->escribir(contenido, 0, bq_con_oflow->get_tope());
	delete[](contenido);
	this->bloque = aux;
	delete(bq_con_oflow);
}
unsigned int BloqueSec::get_mitad() {
	unsigned int media = this->bloque->get_tope() / 2;
	int dist_min = INT_MAX;
	unsigned int pos_min = UINT_MAX;
	int dist_aux = INT_MAX;
	unsigned int pos_act = PRIMERO_BLOQUE_SEC;
	unsigned int tam_act = 0;
	TipoStruct* aux;
	if (!this->vacio()) {
		do {
			dist_aux = pos_act - media;
			if (dist_aux < 0) dist_aux*=-1;
			if (dist_aux < dist_min){
				dist_min = dist_aux;
				pos_min = pos_act;
			}
			aux = (TipoStruct*) this->reg_base->clonar();
			this->bloque->get_registro(aux, pos_act);
			tam_act = aux->get_tamanio_serializado();
			delete (aux);
			pos_act += tam_act;
		}
		while (pos_act < this->bloque->get_tope());
		return pos_min;
	} else
		return 0;
}
